Merge pull request #939 from cantino/dry_run_with_event

Add support for sending an event in a "Dry Run"

Akinori MUSHA 8 jaren geleden
bovenliggende
commit
67ff37a9f8

+ 61 - 4
app/assets/javascripts/components/utils.js.coffee

@@ -34,10 +34,67 @@ class @Utils
34 34
     body?(modal.querySelector('.modal-body'))
35 35
     $(modal).modal('show')
36 36
 
37
-  @handleDryRunButton: (button, data = $(button.form).serialize()) ->
37
+  @handleDryRunButton: (button, data = if button.form then $(':input[name!="_method"]', button.form).serialize() else '') ->
38 38
     $(button).prop('disabled', true)
39
+    cleanup = -> $(button).prop('disabled', false)
40
+
41
+    url = $(button).data('action-url')
42
+    with_event_mode = $(button).data('with-event-mode')
43
+
44
+    if with_event_mode is 'no'
45
+      return @invokeDryRun(url, data, cleanup)
46
+
47
+    Utils.showDynamicModal """
48
+      <h5>Event to send#{if with_event_mode is 'maybe' then ' (Optional)' else ''}</h5>
49
+      <form class="dry-run-form" method="post">
50
+        <div class="form-group">
51
+          <textarea rows="10" name="event" class="payload-editor" data-height="200">
52
+            {}
53
+          </textarea>
54
+        </div>
55
+        <div class="form-group">
56
+          <input value="Dry Run" class="btn btn-primary" type="submit" />
57
+        </div>
58
+      </form>
59
+      """,
60
+      body: (body) =>
61
+        form = $(body).find('.dry-run-form')
62
+        payload_editor = form.find('.payload-editor')
63
+        if previous = $(button).data('payload')
64
+          payload_editor.text(previous)
65
+        window.setupJsonEditor(payload_editor)
66
+        form.submit (e) =>
67
+          e.preventDefault()
68
+          json = $(e.target).find('.payload-editor').val()
69
+          json = '{}' if json == ''
70
+          try
71
+            payload = JSON.parse(json)
72
+            throw true unless payload.constructor is Object
73
+            if Object.keys(payload).length == 0
74
+              json = ''
75
+            else
76
+              json = JSON.stringify(payload)
77
+          catch
78
+            alert 'Invalid JSON object.'
79
+            return
80
+          if json == ''
81
+            if with_event_mode is 'yes'
82
+              alert 'Event is required for this agent to run.'
83
+              return
84
+            dry_run_data = data
85
+            $(button).data('payload', null)
86
+          else
87
+            dry_run_data = "event=#{encodeURIComponent(json)}&#{data}"
88
+            $(button).data('payload', json)
89
+          $(body).closest('[role=dialog]').on 'hidden.bs.modal', =>
90
+            @invokeDryRun(url, dry_run_data, cleanup)
91
+          .modal('hide')
92
+      title: 'Dry Run'
93
+      onHide: cleanup
94
+
95
+  @invokeDryRun: (url, data, callback) ->
39 96
     $('body').css(cursor: 'progress')
40
-    $.ajax type: 'POST', url: $(button).data('action-url'), dataType: 'json', data: data
97
+    $.ajax type: 'POST', url: url, dataType: 'json', data: data
41 98
       .always =>
42 99
         $('body').css(cursor: 'auto')
43 100
       .done (json) =>
@@ -55,7 +112,7 @@ class @Utils
55 112
               find('.agent-dry-run-events').text(json.events).end().
56 113
               find('.agent-dry-run-memory').text(json.memory)
57 114
           title: 'Dry Run Results',
58
-          onHide: -> $(button).prop('disabled', false)
115
+          onHide: callback
59 116
       .fail (xhr, status, error) ->
60 117
         alert('Error: ' + error)
61
-        $(button).prop('disabled', false)
118
+        callback()

+ 7 - 2
app/concerns/dry_runnable.rb

@@ -1,7 +1,7 @@
1 1
 module DryRunnable
2 2
   extend ActiveSupport::Concern
3 3
 
4
-  def dry_run!
4
+  def dry_run!(event = nil)
5 5
     @dry_run = true
6 6
 
7 7
     log = StringIO.new
@@ -13,7 +13,12 @@ module DryRunnable
13 13
     begin
14 14
       raise "#{short_type} does not support dry-run" unless can_dry_run?
15 15
       readonly!
16
-      check
16
+      if event
17
+        raise "This agent cannot receive an event!" unless can_receive_events?
18
+        receive([event])
19
+      else
20
+        check
21
+      end
17 22
     rescue => e
18 23
       error "Exception during dry-run. #{e.message}: #{e.backtrace.join("\n")}"
19 24
     end

+ 8 - 2
app/controllers/agents_controller.rb

@@ -37,7 +37,7 @@ class AgentsController < ApplicationController
37 37
   def dry_run
38 38
     attrs = params[:agent] || {}
39 39
     if agent = current_user.agents.find_by(id: params[:id])
40
-      # PUT /agents/:id/dry_run
40
+      # POST /agents/:id/dry_run
41 41
       if attrs.present?
42 42
         type = agent.type
43 43
         agent = Agent.build_for_type(type, current_user, attrs)
@@ -50,7 +50,13 @@ class AgentsController < ApplicationController
50 50
     agent.name ||= '(Untitled)'
51 51
 
52 52
     if agent.valid?
53
-      results = agent.dry_run!
53
+      if event_payload = params[:event]
54
+        dummy_agent = Agent.build_for_type('ManualEventAgent', current_user, name: 'Dry-Runner')
55
+        dummy_agent.readonly!
56
+        event = dummy_agent.events.build(user: current_user, payload: event_payload)
57
+      end
58
+
59
+      results = agent.dry_run!(event)
54 60
 
55 61
       render json: {
56 62
         log: results[:log],

+ 12 - 0
app/helpers/agent_helper.rb

@@ -37,4 +37,16 @@ module AgentHelper
37 37
       }.join(delimiter).html_safe
38 38
     end
39 39
   end
40
+
41
+  def agent_dry_run_with_event_mode(agent)
42
+    case
43
+    when agent.cannot_receive_events?
44
+      'no'.freeze
45
+    when agent.cannot_be_scheduled?
46
+      # incoming event is the only trigger for the agent
47
+      'yes'.freeze
48
+    else
49
+      'maybe'.freeze
50
+    end
51
+  end
40 52
 end

+ 1 - 1
app/views/agents/_action_menu.html.erb

@@ -7,7 +7,7 @@
7 7
 
8 8
   <% if agent.can_dry_run? %>
9 9
     <li>
10
-      <%= link_to icon_tag('glyphicon-refresh') + ' Dry Run', '#', 'data-action-url' => dry_run_agent_path(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this, '_method=PUT')" %>
10
+      <%= link_to icon_tag('glyphicon-refresh') + ' Dry Run', '#', 'data-action-url' => dry_run_agent_path(agent), 'data-with-event-mode' => agent_dry_run_with_event_mode(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this)" %>
11 11
     </li>
12 12
   <% end %>
13 13
 

+ 1 - 1
app/views/agents/_options.erb

@@ -25,6 +25,6 @@
25 25
 <div class="form-group">
26 26
   <%= submit_tag "Save", :class => "btn btn-primary" %>
27 27
   <% if agent.can_dry_run? %>
28
-    <%= button_tag class: 'btn btn-default agent-dry-run-button', type: 'button', 'data-action-url' => agent.persisted? ? dry_run_agent_path(agent) : dry_run_agents_path do %><%= icon_tag('glyphicon-refresh') %> Dry Run<% end %>
28
+    <%= button_tag class: 'btn btn-default agent-dry-run-button', type: 'button', 'data-action-url' => agent.persisted? ? dry_run_agent_path(agent) : dry_run_agents_path, 'data-with-event-mode' => agent_dry_run_with_event_mode(agent) do %><%= icon_tag('glyphicon-refresh') %> Dry Run<% end %>
29 29
   <% end %>
30 30
 </div>

+ 1 - 1
config/routes.rb

@@ -2,7 +2,7 @@ Huginn::Application.routes.draw do
2 2
   resources :agents do
3 3
     member do
4 4
       post :run
5
-      put :dry_run
5
+      post :dry_run
6 6
       post :handle_details_post
7 7
       put :leave_scenario
8 8
       delete :remove_events

+ 46 - 17
spec/concerns/dry_runnable_spec.rb

@@ -7,10 +7,22 @@ describe DryRunnable do
7 7
     can_dry_run!
8 8
 
9 9
     def check
10
+      perform
11
+    end
12
+
13
+    def receive(events)
14
+      events.each do |event|
15
+        perform(event.payload['prefix'])
16
+      end
17
+    end
18
+
19
+    private
20
+
21
+    def perform(prefix = nil)
10 22
       log "Logging"
11
-      create_event payload: { 'test' => 'foo' }
23
+      create_event payload: { 'test' => "#{prefix}foo" }
12 24
       error "Recording error"
13
-      create_event payload: { 'test' => 'bar' }
25
+      create_event payload: { 'test' => "#{prefix}bar" }
14 26
       self.memory = { 'last_status' => 'ok', 'dry_run' => dry_run? }
15 27
       save!
16 28
     end
@@ -46,21 +58,6 @@ describe DryRunnable do
46 58
     expect(messages).to eq(['Logging', 'Recording error'])
47 59
   end
48 60
 
49
-  it "traps logging, event emission and memory updating, with dry_run? returning true" do
50
-    results = nil
51
-
52
-    expect {
53
-      results = @agent.dry_run!
54
-      @agent.reload
55
-    }.not_to change {
56
-      [@agent.memory, counts]
57
-    }
58
-
59
-    expect(results[:log]).to match(/\AI, .+ INFO -- : Logging\nE, .+ ERROR -- : Recording error\n/)
60
-    expect(results[:events]).to eq([{ 'test' => 'foo' }, { 'test' => 'bar' }])
61
-    expect(results[:memory]).to eq({ 'last_status' => 'ok', 'dry_run' => true })
62
-  end
63
-
64 61
   it "does not perform dry-run if Agent does not support dry-run" do
65 62
     stub(@agent).can_dry_run? { false }
66 63
 
@@ -77,4 +74,36 @@ describe DryRunnable do
77 74
     expect(results[:events]).to eq([])
78 75
     expect(results[:memory]).to eq({})
79 76
   end
77
+
78
+  describe "dry_run!" do
79
+    it "traps any destructive operations during a run" do
80
+      results = nil
81
+
82
+      expect {
83
+        results = @agent.dry_run!
84
+        @agent.reload
85
+      }.not_to change {
86
+        [@agent.memory, counts]
87
+      }
88
+
89
+      expect(results[:log]).to match(/\AI, .+ INFO -- : Logging\nE, .+ ERROR -- : Recording error\n/)
90
+      expect(results[:events]).to eq([{ 'test' => 'foo' }, { 'test' => 'bar' }])
91
+      expect(results[:memory]).to eq({ 'last_status' => 'ok', 'dry_run' => true })
92
+    end
93
+
94
+    it "traps any destructive operations during a run when an event is given" do
95
+      results = nil
96
+
97
+      expect {
98
+        results = @agent.dry_run!(Event.new(payload: { 'prefix' => 'super' }))
99
+        @agent.reload
100
+      }.not_to change {
101
+        [@agent.memory, counts]
102
+      }
103
+
104
+      expect(results[:log]).to match(/\AI, .+ INFO -- : Logging\nE, .+ ERROR -- : Recording error\n/)
105
+      expect(results[:events]).to eq([{ 'test' => 'superfoo' }, { 'test' => 'superbar' }])
106
+      expect(results[:memory]).to eq({ 'last_status' => 'ok', 'dry_run' => true })
107
+    end
108
+  end
80 109
 end

+ 13 - 0
spec/controllers/agents_controller_spec.rb

@@ -377,6 +377,19 @@ describe AgentsController do
377 377
         [users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at]
378 378
       }
379 379
     end
380
+
381
+    it "accepts an event" do
382
+      sign_in users(:bob)
383
+      agent = agents(:bob_website_agent)
384
+      url_from_event = "http://xkcd.com/?from_event=1".freeze
385
+      expect {
386
+        post :dry_run, id: agent, event: { url: url_from_event }
387
+      }.not_to change {
388
+        [users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at]
389
+      }
390
+      json = JSON.parse(response.body)
391
+      expect(json['log']).to match(/^I, .* : Fetching #{Regexp.quote(url_from_event)}$/)
392
+    end
380 393
   end
381 394
 
382 395
   describe "DELETE memory" do